package sma.organizations.roles;

import java.util.ArrayList;
import java.util.List;

import javax.vecmath.Vector2d;

import org.janusproject.kernel.address.Address;
import org.janusproject.kernel.address.AgentAddress;
import org.janusproject.kernel.agentsignal.QueuedSignalAdapter;
import org.janusproject.kernel.crio.core.Role;
import org.janusproject.kernel.message.Message;
import org.janusproject.kernel.status.Status;
import org.janusproject.kernel.status.StatusFactory;

import sma.agents.StandartAgent;
import sma.agents.StandartAgent.GuiChannelImpl;
import sma.agents.bodies.CircleBody;
import sma.agents.channels.GuiChannel;
import sma.agents.channels.DirectionChannel;
import sma.agents.listeners.DeleteListener;
import sma.agents.listeners.MoveGoalListener;
import sma.agents.listeners.MoveListener;
import sma.environment.LocalEnvironment;
import sma.organizations.messages.MassCenterMessage;
import sma.organizations.messages.MassCenterResponseMessage;
import sma.organizations.signals.SendMoveSignal;
import sma.organizations.signals.SendDirectionSignal;
import appli.utils.Config;
import appli.utils.PointPondere;

/**
 * Goal est le role tenu par un GoalAgent qui a pour but de definir un es
 * objectifs du vehicule.
 * 
 * @author Etienne Sainton
 * @version 0.1
 * 
 */
public class MassCenter extends Role {

	/**
	 * Sa coordonnee courante en X
	 */
	private int currentDivX;
	/**
	 * Sa coordonnee courante en Y
	 */
	private int currentDivY;

	/**
	 * Son environnement local
	 */
	private LocalEnvironment environment;

	/**
	 * Son body
	 */
	private CircleBody body;

	/**
	 * Le mouvement qu'il va devoir effectuer au prochain cycle de vie
	 */
	public static final String MOVE = "NONE";

	/**
	 * L'etat courant de l'agent
	 */
	private State currentState = State.I_M_HERE;

	/**
	 * Liste des points ponderes des positivesAgents
	 */

	private List<PointPondere> positiveBodies;
	
	/**
	 * Le coeff k
	 */
	private double k = 50;

	
	/**
	 * Initialisation de l'agent. Automatiquement appelee par le constructeur
	 * 
	 * @param _environment l'environnement dans lequel évolue l'agent
	 * @param _body la représentation graphique de l'agent
	 */
	private void init(LocalEnvironment _environment, CircleBody _body, double k) {
		environment = _environment;
		body = _body;
		currentDivX = Config.PANEL_SIZE - body.getDiameter() / 2;
		currentDivY = Config.PANEL_SIZE - body.getDiameter() * 2;
		
		this.k = k;
		this.body.setPosition(new Vector2d(currentDivX, currentDivY));
	}

	/**
	 * Override de l'activate defini par Janus. Nous en profitons pour lui
	 * affecter son environnement et son body.
	 */
	@Override
	public Status activate(Object... params) {
		init((LocalEnvironment) params[0], (CircleBody) params[1], (Double) params[2]);

		if (Config.DEBUG == true)
			System.out.println("Le role Gravity est initialisé");

		return StatusFactory.ok(this);
	}

	/**
	 * Override du live defini par Janus. Le raisonnement est le principe : a
	 * chaque iteration, je regarde la position des positives agents, puis je
	 * recalcule ma position.
	 */
	@Override
	public Status live() {
		
		run();
		return StatusFactory.ok(this);
	}

	private void run() {
		// Selon l'état dans lequel voyons ce qu'on fait ?
		switch (this.currentState) {
		case I_M_HERE:
			// Reponds à l'agent qui pilote la voiture
			// non implemente pour l'instant

			// TODO : modifier ça pour que ce ne soient que les agents de mon
			// groupe qui soient concernés
			broadcastMessage(DecisiveMassCenter.class, new MassCenterMessage());

			// Et ensuite on attends les messages des positives agents :
			this.currentState = State.WAITING_PERCEIVED;
		case WAITING_PERCEIVED:
			// Je reçois la liste des agents que je peux voir :
			this.positiveBodies = getPositiveAgentsPosition();
			this.currentState = State.MOVING;
		case MOVING:
			move();
			this.currentState = State.I_M_HERE;
		}

	}

	/**
	 * Permet de lister les positive agents :
	 * 
	 * @return Liste des corps des agents perçus :
	 */
	private List<PointPondere> getPositiveAgentsPosition() {
		// La liste des agents vus :
		List<PointPondere> tmpPositiveBodies = new ArrayList<PointPondere>();

		// Listons les agents qui nous ont répondu qu'ils nous voyaient :
		for (Message msg : getMailbox()) {
			if (msg instanceof MassCenterResponseMessage) {
				// Un positive agent repond en donnant sa position, on l'ajoute
				// a la liste
				tmpPositiveBodies
						.add(((MassCenterResponseMessage) msg).getPoint());

			} else {
				// Ici je ne suis pas censé avoir de message, tous les cas
				// devraient être traités
				if (Config.DEBUG == true)
					System.out
							.println("Un message inatendu vient d'arriver chez le MassCenter");
			}
		}
		return tmpPositiveBodies;
	}

	/**
	 * Cette fonction permet de recuperer le dernier mouvement envoye au
	 * clavier. Ensuite elle calcul la prochaine position en fonction de la
	 * direction et de la finesse du terrain specifiee dans le fichier config.
	 * La mouvement sera applique dans le live (qui appelle cette focntion
	 * d'ailleurs).
	 */
	private void move() {

		Vector2d movement;

		movement = computeMasscenter(positiveBodies);

		//On fait smooth le déplacement :
		movement.sub(body.getPosition());
		movement.scale(0.1);
		movement.add(body.getPosition());
		body.setPosition(movement);
		fireSignal(new SendMoveSignal(this));

		fireSignal(new SendDirectionSignal(this, body.getPosition()));
	}

	/**
	 * Calcul le barycentre des points donnes en parametre
	 * 
	 * @param tmpPositiveBodies Liste des agentsDecisive
	 * @return vecteur correspondant au coordonnee du barycentre
	 */
	private Vector2d computeMasscenter(List<PointPondere> tmpPositiveBodies) {
		// Pour calculer le barycentre, on additionne toutes les ponderations
		// (totalweight)
		// puis pour chaque point, on additionne les positions suivant la
		// formule :
		// poids/totalPoids * vec

		Vector2d position = new Vector2d(0,0);
		Vector2d totalVitesse = new Vector2d(0,0);
		Vector2d vitesse;
		//Vector2d energie = new Vector2d(0,0);

		double total = 0;
		// Calcul de la vitesse totale :
		for (PointPondere point : tmpPositiveBodies) {
			Vector2d tmpVitesse = new Vector2d(point.getVitesse());
			tmpVitesse.scale(k*point.getEnergie().length());
			Vector2d tmpPosition = new Vector2d(point.getPosition());
			//Si l'agent est dans le second groupe alors il faut décaler sa position dans le calcul :
			if(point.getGroupe() == Config.TELEMETER_PORT.get(1)) {				
				tmpPosition.y = tmpPosition.y + 400;
			}

			tmpPosition.scale(k*point.getEnergie().length());
			
			totalVitesse.add(tmpVitesse);
			position.add(tmpPosition);
			
			//On incrémente total :
			total += k*point.getEnergie().length();
		}
		
		
		Vector2d g;
		
		//Le coeff de ponderation, évite un bug :
		if(tmpPositiveBodies.size() != 0) {
			
			double pondere = 1. / total;
			if(pondere < 50) {
				//System.out.println(pondere);
				//Donc la vitesse moyenne :
				vitesse = totalVitesse;
				vitesse.scale(pondere);
	
				// Calcul de la position moyenne
				position.scale(pondere);
				
				//Calcul du vecteur de sortie :
				g = new Vector2d(vitesse);
				g.scale(pondere);
				g.add(position);
			}else{
				g = new Vector2d(this.body.getPosition());
			}
		}else{
			g = new Vector2d(this.body.getPosition());
		}
		
		return g;
	}

	
	
	/**
	 * Etats d'un Gravity
	 */
	public enum State {
		/**
		 * Envoie de sa position aux agents
		 */
		I_M_HERE,
		/**
		 * En attente des agents perceptibles
		 */
		WAITING_PERCEIVED,
		/**
		 * Deplacement
		 */
		MOVING,
	}
	
	
}
